import os
from importlib import import_module
import re

import src.core.util.log as log

from src.core.interface.iTask import ITask

SQL_TO_JAVA_MAP = {
    'bigint': 'Long',
    'tinyint': 'Integer',
    'varchar': 'String',
    'date': 'Date',
    'datetime': 'Date',
    'timestamp': 'Date',
    'int': 'Integer',
    'decimal': 'Double',
    'char': 'String',
    'double': 'Double',
    'text': 'String',
    'smallint': 'Integer'
}

SQL_TO_MYBATIS_MAP = {
    'date': 'TIMESTAMP',
    'datetime': 'TIMESTAMP',
    'timestamp': 'TIMESTAMP',
    'int': 'INTEGER',
    'text': 'LONGVARCHAR'
}


def to_camelcase(text: str) -> str:
    """
    _ to camel case
    :param text: str in _ format
    :return: str in camel case
    """
    arr = filter(None, text.lower().split('_'))
    res = ''
    j = 0
    for i in arr:
        if j == 0:
            res = i
        else:
            res += i[0].upper() + i[1:]
        j += 1
    return res


def find_java_type(sql_type: str) -> str:
    """
    find java type from sql type
    :param sql_type: sql type
    :return: java type
    """
    if sql_type.startswith('varchar'):
        return 'String'
    if sql_type.lower() in SQL_TO_JAVA_MAP:
        return SQL_TO_JAVA_MAP[sql_type.lower()]
    else:
        log.warn('type {} does not map to any java type, return String in default'.format(sql_type))
        return 'String'


def find_mybatis_jdbc_type(sql_type: str) -> str:
    """
    find mybatis jdbc type from sql type
    :param sql_type: sql type
    :return: mybatis jdbc type
    """
    striped_type = strip_parentheses(sql_type).lower()
    if striped_type in SQL_TO_MYBATIS_MAP:
        return SQL_TO_MYBATIS_MAP[striped_type]
    return striped_type.upper()


def write_file(file_name: str, all_column_data: [[str]], task_obj: ITask) -> None:
    """
    write data to file
    :param file_name: target file name
    :param all_column_data: data containing all column information
    :param task_obj: task that will be executed
    :return: None
    """
    with open(file_name, 'w+') as f:
        for i, row in enumerate(all_column_data):
            build_result = task_obj.build_column_str(row, i, len(all_column_data))
            f.write(build_result)


def mkdir(path: str) -> None:
    """
    make directory if not exist
    :param path: directory path
    :return: None
    """
    folder = os.path.exists(path)
    if not folder:
        os.makedirs(path)


def get_default_tasks() -> [ITask]:
    """
    get default tasks
    """
    return __get_tasks_from_module__('src.core.task.default_tasks')


def get_user_tasks() -> [ITask]:
    """
    get user defined tasks
    """
    return __get_tasks_from_module__('src.user_tasks')


def __get_tasks_from_module__(module_name: str) -> [ITask]:
    """
    get task instances from module
    :param module_name: module name
    :return: task instances
    """
    impl_module = import_module(module_name)
    return [cls() for name, cls in vars(impl_module).items()
            if isinstance(cls, type) and issubclass(cls, ITask) and not cls == ITask]


def strip_parentheses(word: str) -> str:
    """
    strip parentheses and things inside it. For example, "varchar(100)" to "varchar"
    :param word: word
    :return: striped word
    """
    return re.sub('\(.*\)', '', word)


def write_sql_search_fields(column_data: [str], is_multi: bool = True, param_idx: int = 0) -> str:
    """
    write search fields
    :param column_data: see definition of ITask
    :param is_multi: flag to indicate whether the parameter in dao method is single
    :param param_idx: indicate the position of parameter in the dao method
    :return: built string
    """
    camel_name = to_camelcase(column_data[0])
    java_type = find_java_type(column_data[1])
    placeholder = ''
    if is_multi:
        placeholder = '_parameter.get("{}").'.format(param_idx)
    placeholder += camel_name
    if java_type == 'String':
        return ('<if test=\'{} != null\'>\n'.format(placeholder) +
                '    AND LOWER(`{}`) LIKE CONCAT(\'%\', CONCAT(LOWER(#{{{},jdbcType={}}}), \'%\'))\n'
                .format(column_data[0], placeholder, find_mybatis_jdbc_type(column_data[1])) +
                '</if>\n')
    else:
        return ('<if test=\'{} != null\'>\n'.format(placeholder) +
                '    AND `{}` = #{{{},jdbcType={}}}\n'
                .format(column_data[0], placeholder, find_mybatis_jdbc_type(column_data[1])) +
                '</if>\n')
